/**
    ATClab - scriptable Air Traffic Control simulations.
    Copyright (C) 2010 openatclab@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

#include "aircraftsprite.h"

#include "aircraft_agent.h"
#include "aircraft_data.h"
#include "acsprite.h"
#include "actools.h"
#include "atc.h"
#include "canvas.h"
#include <q3canvas.h>
#include <qregexp.h>
#include <qtimer.h>
//Added by qt3to4:
#include <Q3PointArray>
#include <algorithm>

using namespace atc;
using std::make_pair;
using std::string;


///////////////////////////////////////////////////////////////////////////////
//
//
//
//
//-----------------------------------------------------------------------------
// construction/destruction
//

/*!
 *
 */
AircraftSprite::AircraftSprite( Canvas *c, AircraftAgent *ac ) 
	  : _aircraft( ac )
	  , _canvas( c )
	  , _sprite()
	  , _locator( new ACLocator( c, this ) )
	  , _callout( new ACCallOut( c, this ) )
	  , _datablk( new ACDataBlk( c, this ) )
	  , _callblk( new ACCallBlk( c, this ) )
	  , _altblk ( new ACAltBlk( c, this ) )
	  , _velblk ( new ACVelBlk( c, this ) )
	  , _velvector( new ACProbeTool( c, this ) )
	  , _history  ( new ACHistoryTool( c, this ) )
	  , _route    ( new ACRouteTool( c, this ) )
	  , _x( 0 ), _y( 0 )
	  , _update( true )
	  , _visible( false )
	  , _colour( Qt::black )
	  , _flash_timer( new QTimer( this ) )
	  , _blinkon( true )
{
	create_locator( );
	create_callout( );
	create_textblk( );
	create_vector( );

	build_outline( c );

	set_call( _aircraft->callsign() );
	set_data( _aircraft->type() );

    connect( _flash_timer, SIGNAL( timeout() ), SLOT( blink() ) );

	connect( 
			this,    SIGNAL( event_recorded( KeyValues& ) )
		  , _canvas, SIGNAL( event_recorded( KeyValues& ) )
	);


}

/*!
 *
 */
AircraftSprite::~AircraftSprite() {
	delete _route;
	delete _history;
	delete _velvector;
}


//-----------------------------------------------------------------------------
// [public] interface
//

/*!
 *
 */
void AircraftSprite::update() {
	if ( ! _update ) return;
	if ( ! _aircraft->is_active() ) return;

	set_alt( _aircraft->z(), _aircraft->calt() );
	set_vel( _aircraft->speed() );
	move_to( _aircraft->x(), _aircraft->y() );

	_velvector->update();
	_history->update();
	_route->update();

	set_colour( color_lookup() );
	set_flash( flash_lookup() );
	set_visible( _aircraft->is_active() );

	_canvas->update();
}

//-----------------------------------------------------------------------------
// [public] interface
//

/*!
 * Canvas co-ordinate position
 */
double AircraftSprite::cx() const { return _x; }
double AircraftSprite::cy() const { return _y; }

/*!
 *
 */
double AircraftSprite::v() const { return _aircraft->speed(); }
double AircraftSprite::a() const { return _aircraft->a(); }

/*!
 * @todo: use iterators
 */
const AircraftPath & AircraftSprite::path() const {
	return _aircraft->path();
}

/*!
 *
 */
const AircraftHistory & AircraftSprite::history() const {
	return _aircraft->history();
}

//-----------------------------------------------------------------------------
// [public] mutator interface
//

/*!
 *
 */
void AircraftSprite::set_data( const char * data ){
	_datablk->setText( QString( data ) );
}

/*!
 *
 */
void AircraftSprite::set_call( const char * call ){
	_callblk->setText( QString( call ) );
}

/*!
 *
 */
void AircraftSprite::set_alt( double alt, double cfl ) {
	int ialt = (int)( alt/100.0 + ZERO_PT_FIVE );
	int icfl = (int)( cfl/100.0 + ZERO_PT_FIVE );
	_altblk->setText( QString( "%1%2%3" )
			.arg( ialt )
			.arg( alt < cfl ? "^" : alt > cfl ? "v" : ">" )
			.arg( icfl )
	);
}

/*!
 *
void AircraftSprite::set_alt( double altitude ){
	double alt, cfl;
	decode_altblk( alt, cfl );	// retrieve data from text
	set_alt( altitude, cfl );
}
 */

/*!
 *
void AircraftSprite::set_cfl( double cleared ){
	double alt, cfl;
	decode_altblk( alt, cfl );	// retrieve data from text
	set_alt( alt, cleared );
}
 */

/*!
 *
 */
void AircraftSprite::set_vel( double velocity ){
	_velblk->setText( QString("%1").arg( (int)( velocity/10.0 + ZERO_PT_FIVE ) ) );
}

/*!
 * Move the complex sprite.
 */
void AircraftSprite::move_to( const double x, const double y ) {
	std::pair< double, double > pos = convert2canvas(  x,  y );

	MoveBy move_by( pos.first - _x, pos.second - _y );
	std::for_each( _sprite.begin(), _sprite.end(), move_by );

	_x = pos.first;
	_y = pos.second;
}


/*!
 * Convert user measurement to canvas
 */
std::pair< double, double > AircraftSprite::convert2canvas( 
		const double x, const double y 
) const {
	return _canvas->to_internal_point( x, y );
}

/*!
 * Set the colour of complex sprite
 */
void AircraftSprite::set_colour( const QColor &paint ) {
	if ( paint == _colour ) return;
	_colour = paint;
	std::for_each( _sprite.begin(), _sprite.end(), SetColour( paint ) );
}

/*!
 * Set the visible state of complex sprite
 */
void AircraftSprite::set_visible( bool status ) {
	if ( status == _visible ) return;
	_visible = status;
	std::for_each( _sprite.begin(), _sprite.end(), SetVisible( status ) );

	if ( !_visible ) {
		_history->set_visible( false );
		_route->set_active( false );
		_velvector->setVisible( false );
	}
}

/*!
 *
 */
void AircraftSprite::set_flash( bool flash ) {
	if ( flash )
		_flash_timer->start( ACSPRITE_BLINK_PERIOD );
	else
		_flash_timer->stop();
}

/*!
 *
 */
void AircraftSprite::rotate_callout() {
	// current points
	QPoint p1( _callout->startPoint() );
	QPoint p2( _callout->endPoint() );

	int delta = 2 * AIRCRAFT_SPRITE_CALLOUT;
	int fact = p1.x() > 0 ? -1 : 1;
	QFontMetrics fm( _callblk->font() );

	if (
			( p1.x() < 0 && p1.y() < 0 ) 
		 || ( p1.x() > 0 && p1.y() > 0 )
	){
		_callout->setPoints( p1.x(), -p1.y(), p2.x(), -p2.y() );
		
		int d = fact * ( delta - 3 * fm.lineSpacing() );
		_callblk->moveBy(0, d);
		_datablk->moveBy(0, d);
		_altblk->moveBy(0, d);
		_velblk->moveBy(0, d);
	}
	else {
		_callout->setPoints( -p1.x(), p1.y(), -p2.x(), p2.y() );

		_callblk->moveBy(fact * (delta + _callblk->boundingRect().width()), 0);
		_datablk->moveBy(fact * (delta + _datablk->boundingRect().width()), 0);
		_altblk->moveBy(
			fact * (
					delta 
				  + 8 * fm.charWidth("0", 0) 
				  + _velblk->boundingRect().width()
			), 
			0
		);
		_velblk->moveBy(
			fact * (
					delta 
				  + 8 * fm.charWidth("0", 0) 
				  + _velblk->boundingRect().width()
			), 
			0
		);
	}

	_canvas->update();

	KeyValues kvs;
	kvs.push_back( make_pair( string( "action" ), string( "rotate_callout" ) ) );
	kvs.push_back( make_pair( string( "callsign" ), to_string( _aircraft->callsign() ) ) );
	emit event_recorded( kvs );
}

/*!
 * Toggle ON/OFF route information
 */
void AircraftSprite::toggle_route() {
	_route->toggle();

	KeyValues kvs;
	kvs.push_back( make_pair( string( "tool" ), string( "route" ) ) );
	kvs.push_back( make_pair( string( "active" ), to_string( _route->is_active() ) ) );
	kvs.push_back( make_pair( string( "callsign" ), to_string( _aircraft->callsign() ) ) );
	emit event_recorded( kvs );

}

/*!
 * Toggle ON/OFF history points
 */
void AircraftSprite::toggle_history() {
	_history->toggle();

	KeyValues kvs;
	kvs.push_back( make_pair( string( "tool" ), string( "history" ) ) );
	kvs.push_back( make_pair( string( "active" ), to_string( _history->is_visible() ) ) );
	kvs.push_back( make_pair( string( "callsign" ), to_string( _aircraft->callsign() ) ) );
	emit event_recorded( kvs );
}

/*!
 * Toggle ON/OFF short route probe
 */
void AircraftSprite::toggle_probe() {
	_velvector->toggle();

	KeyValues kvs;
	kvs.push_back( make_pair( string( "tool" ), string( "short_route_probe" ) ) );
	kvs.push_back( make_pair( string( "value" ), to_string( _velvector->value() ) ) );
	kvs.push_back( make_pair( string( "callsign" ), to_string( _aircraft->callsign() ) ) );
	emit event_recorded( kvs );
}

/*!
 * 
 */
void AircraftSprite::vector( double x, double y ){
	Point p( _canvas->to_user_point( x, y ) );
	_aircraft->vector( p.first, p.second );
}

/*!
 * 
 */
void AircraftSprite::accept() { 
	_aircraft->accept();  
	update();
}

/*!
 * 
 */
void AircraftSprite::handoff(){ 
	_aircraft->handoff(); 
	update();
}

//-----------------------------------------------------------------------------
// [public] interaction
//

/*!
 *
 */
bool AircraftSprite::locator_selected( const QPoint &pt ) const {
	return QRegion( _locator->boundingRect() ).contains( pt );
}

/*!
 *
 */
void AircraftSprite::reroute( const AircraftPathToken *t, const Point &p ) {
	_aircraft->reroute( t, p );
}

//-----------------------------------------------------------------------------
// [public] slots
//

/*!
 *
 */
void AircraftSprite::next_waypoint() { /*_route->next();*/ }

/*!
 *
 */
void AircraftSprite::destroy() {
	_route->set_active( false );
	_history->set_visible( false );
	_velvector->deactivate();
	set_visible( false );
}


//-----------------------------------------------------------------------------
// [private] initialization
//

/*!
 *
 */
void AircraftSprite::create_locator( ) {
	_locator->setBrush( AIRCRAFT_SPRITE_DEFAULT_BRUSH );
	_locator->setSize( AIRCRAFT_SPRITE_DIAMETER, AIRCRAFT_SPRITE_DIAMETER );
	add2sprite( _locator );
	_locator->setZ( AIRCRAFT_LOCATOR_LAYER );	// reset layer to higher precedence
	_locator->show();
}

/*!
 *
 */
void AircraftSprite::create_callout( ) {
	_callout->setPoints(
		  AIRCRAFT_SPRITE_DIAMETER / 2, -AIRCRAFT_SPRITE_DIAMETER / 2
		, AIRCRAFT_SPRITE_CALLOUT, -AIRCRAFT_SPRITE_CALLOUT
	);
	add2sprite( _callout );
}

/*!
 *
 */
void AircraftSprite::create_textblk( ) {
	init_textblk( _callblk, 0, 0 );
	init_textblk( _datablk, 1, 0 );
	init_textblk( _altblk,  2, 0 );
	init_textblk( _velblk,  2, 10 );
} 

/*!
 *
 */
void AircraftSprite::init_textblk( Q3CanvasText *txt, int line, int column ) {
	txt->setFont( AIRCRAFT_SPRITE_FONT );

	QFontMetrics fmtx( txt->font() );
	txt->setText( EMPTY_STRING );
	txt->moveBy( 
			 AIRCRAFT_SPRITE_CALLOUT + column * fmtx.charWidth( "0", 0 )
		  , -AIRCRAFT_SPRITE_CALLOUT +   line * fmtx.lineSpacing() 
	);
	add2sprite( txt );
}

/*!
 * Create Short Route Probe/Velocity Vector
 */
void AircraftSprite::create_vector( ) {
	add2sprite( _velvector );
}

/*!
 *
 */
void AircraftSprite::build_outline( Q3Canvas *canvas ) {
	Q3PointArray pts;
	pts.makeEllipse( 
		0, 0, AIRCRAFT_SPRITE_DIAMETER, AIRCRAFT_SPRITE_DIAMETER 
	);
	QPoint p1( pts.at( pts.size() - 1 ) );
	for ( int i = 0; i < pts.size(); ++i ) {
		QPoint p2( pts[i] );
		Q3CanvasLine *cl = new Q3CanvasLine( canvas );
		cl->setPoints( p1.x(), p1.y(), p2.x(), p2.y() );
		cl->moveBy(
			-AIRCRAFT_SPRITE_DIAMETER / 2, -AIRCRAFT_SPRITE_DIAMETER / 2
		);
		add2sprite( cl );
		p1 = p2;
	}
}

/*! 
 * Collect together canvas items that make up complex sprite.
 */
void AircraftSprite::add2sprite( Q3CanvasItem *item ) {
	item->setZ( AIRCRAFT_SPRITE_LAYER );
	_sprite.push_back( item );
}

//-----------------------------------------------------------------------------
// [private] utility
//

/*!
 *
 */
QColor AircraftSprite::color_lookup() const {
	if ( _aircraft->is_conflicted() ) 
			return _canvas->conflict_colour();
	else
			return _canvas->colour_lookup( _aircraft->control() );
}	

/*!
 *
 */
bool AircraftSprite::flash_lookup() const {
	if ( _aircraft->is_conflicted() ) 
			return _canvas->conflict_blink();
	else
			return _canvas->blink_lookup( _aircraft->control() );
}

/*!
 *
 */
void AircraftSprite::decode_altblk( double &alt, double &cfl ) {
	QRegExp rx( "^\\d?\\d$" );
	rx.search( _altblk->text() );
	alt = rx.cap( 1 ).toDouble();
	cfl = rx.cap( 2 ).toDouble();
}

//-----------------------------------------------------------------------------
// [private slots]
//

/*!
 *
 */
void AircraftSprite::blink() {
	std::for_each( 
			_sprite.begin(), _sprite.end()
		  , SetColour( _blinkon ? _colour : ACSPRITE_BLINK_OFF_COLOUR ) 
	);
	_blinkon = !_blinkon;
	_canvas->update();
}


////////////////////////////////////////////////////////////////////////////////
//
// atc::AircraftSprite::MoveBy functor
//
//------------------------------------------------------------------------------
// [public]
//

/*!
 *
 */
void AircraftSprite::MoveBy::operator ()( Q3CanvasItem *item ) const { 
	item->moveBy( _dx, _dy );
}


////////////////////////////////////////////////////////////////////////////////
//
// atc::AircraftSprite::SetColour functor
//
//------------------------------------------------------------------------------
// [public]
//

/*!
 *
 */
void AircraftSprite::SetColour::operator ()( Q3CanvasItem *item ) const{
	switch ( item->rtti() ) {
	
	case CanvasItem::RTTI_DATABLK :
	case CanvasItem::RTTI_CALLBLK :
	case CanvasItem::RTTI_ALTBLK  :
	case CanvasItem::RTTI_VELBLK  :
	case Q3CanvasItem::Rtti_Text :
		dynamic_cast< Q3CanvasText * >(item)->setColor( _colour );
		break;
	case Q3CanvasItem::Rtti_Sprite :
		// do nothing
		break;
	default : { // QCanvasPolygonalItem
		Q3CanvasPolygonalItem *i = dynamic_cast< Q3CanvasPolygonalItem * >(item);
		QPen p = i->pen();
		p.setColor( _colour );
		i->setPen( p );
//		dynamic_cast< QCanvasPolygonalItem * >(item)->setPen( _colour );
	}
	
	}
}


////////////////////////////////////////////////////////////////////////////////
//
// atc::AircraftSprite::SetVisible functor
//
//------------------------------------------------------------------------------
// [public]
//

/*!
 *
 */
void AircraftSprite::SetVisible::operator ()( Q3CanvasItem *item ) const{ 
	item->setVisible( _status ); 
}


///////////////////////////////////////////////////////////////////////////////
